/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tianyun.cloud.common.constants.ResponseCode;
import org.tianyun.cloud.common.dto.RestResult;
import org.tianyun.cloud.exception.BusinessException;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Rpc远程调用处理工具类
 * <p>
 * <p>
 * 示例代码： <code>
 * Result data = RpcBuilder.ofNonNull()
 * .setLogger("test")
 * .thrownTargetException()
 * // .setFailedCode("xxxx")
 * .call(remoteService::get, params)
 * .get();
 * <p>
 * Optional<Result> data = RpcBuilder.ofNullable()
 * .setLogger("test")
 * .thrownTargetException() // or setFailedCode("xxxx")
 * .call(remoteService::get, params);
 *
 * </code>
 *
 * @auther ebert_chan
 */
public class RpcBuilder {

    private static final Logger LOGGER = LoggerFactory.getLogger(RpcBuilder.class);

    /**
     * 当code不为空时，调用失败将抛出BusinessException，code业务错误码； 未设置时仅记录错误日志， 返回结果为null
     */
    private String code;

    /**
     * BusinessException异常业务参数
     */
    private Object[] args;

    /**
     * 日志标签
     */
    private String logger;

    /**
     * 是否激活对空数据的检查，默认为false
     */
    private boolean checkNullData;

    private boolean thrownTargetException;

    private RpcBuilder() {
    }

    /**
     * 设置调用失败（响应码不为ResponseCode.SUCCESS或调用异常时）的业务异常码，抛出异常BusinessException
     *
     * @param code
     * @return
     * @auther ebert_chan
     */
    public RpcBuilder setFailedCode(String code, Object... args) {
        this.code = code;
        this.args = args;
        return this;
    }

    /**
     * 如果调用失败，使用被调用方返回的异常信息抛出BusinessException异常
     *
     * @return
     * @auther ebert_chan
     */
    public RpcBuilder thrownTargetException() {
        this.thrownTargetException = true;
        return this;
    }

    /**
     * 设置并启用日志输出
     *
     * @param logger
     * @return
     * @auther ebert_chan
     */
    public RpcBuilder setLogger(String logger) {
        this.logger = logger;
        return this;
    }

    /**
     * 设置空数据检查
     *
     * @return
     * @author ebert_chan
     */
    private RpcBuilder enableNullDataCheck() {
        this.checkNullData = true;
        return this;
    }

    /**
     * 是否启用日志输出
     *
     * @return
     * @auther ebert_chan
     */
    public boolean isEnableLogger() {
        return this.logger != null;
    }

    /**
     * 调用前检查
     * <p>
     *
     * @author ebert_chan
     * @date 2021年10月28日
     */
    private void callCheckBefore() {
        if (checkNullData && this.code == null && !thrownTargetException) {
            throw new IllegalArgumentException("RpcBuilder.ofNonNull() must specify failed code. can be use thrownTargetException() or setFailedCode(int code, Object...args)");
        }
    }

    /**
     * 调用远程方法
     *
     * @param func   调用函数
     * @param params 调用参数
     * @return Optional<V> 调用结果
     * @author ebert_chan
     */
    public <V, T> Optional<V> call(Function<T, RestResult<V>> func, T params) {

        this.callCheckBefore();

        RestResult<V> restResult = null;
        try {
            if (this.isEnableLogger()) {
                LOGGER.info("[{}]rpc call request: {}", this.logger, JsonUtils.toJsonString(params));
            }
            restResult = func.apply(params);
            if (this.isEnableLogger()) {
                LOGGER.info("[{}]rpc call response: {}", this.logger, JsonUtils.toJsonString(restResult));
            }

            V result = handlerResponse(restResult);

            return Optional.ofNullable(result);

        } catch (Throwable e) {
            handlerException(e);
        }

        return Optional.empty();
    }

    /**
     * 调用远程方法
     *
     * @param func 调用函数
     * @return Optional<V> 调用结果
     * @author ebert_chan
     */
    public <V> Optional<V> call(Supplier<RestResult<V>> func) {

        this.callCheckBefore();

        RestResult<V> restResult = null;
        try {
            restResult = func.get();
            if (this.isEnableLogger()) {
                LOGGER.info("[{}]rpc call response: {}", this.logger, JsonUtils.toJsonString(restResult));
            }

            V result = handlerResponse(restResult);

            return Optional.ofNullable(result);

        } catch (Throwable e) {
            handlerException(e);
        }

        return Optional.empty();
    }

    /**
     * 处理返回结果
     * <p>
     *
     * @param restResult
     * @return V
     * @author ebert_chan
     */
    private <V> V handlerResponse(RestResult<V> restResult) {
        if (restResult == null) {
            this.loggerOrThrownException(null, "rpc call failed. response is null");
            return null;
        }

        if (ResponseCode.SUCCESS.equals(restResult.getCode())) {
            if (this.checkNullData && restResult.getData() == null) {
                this.loggerOrThrownException(null, "rpc call failed. response 'data' is null");
            }
            return restResult.getData();
        }
        this.loggerOrThrownException(restResult, "rpc call failed. error message: [{}]{}", restResult.getCode(), restResult.getMessage());
        return null;
    }

    /**
     * 打印或抛出异常
     *
     * @param result
     * @param message
     * @param args
     * @auther ebert_chan
     */
    private <V> void loggerOrThrownException(RestResult<V> result, String message, Object... args) {
        if (result == null) {
            if (this.checkNullData) {
                if (this.code != null) {
                    throw new BusinessException(this.code, this.args);
                } else {
                    // thrownTargetException
                    LOGGER.warn((isEnableLogger() ? ("[" + this.logger + "]") : "") + message);
                    throw new BusinessException(ResponseCode.BUSY_EXCEPTION);
                }
            } else if (isEnableLogger()) {
                LOGGER.warn("[" + this.logger + "]" + message, args);
            }
        } else {
            // no success state
            if (thrownTargetException) {
                throw new BusinessException(result.getCode(), result.getMessage());
            } else if (this.code != null) {
                throw new BusinessException(this.code, this.args);
            } else if (this.checkNullData) {
                throw new BusinessException(result.getCode(), result.getMessage());
            } else if (isEnableLogger()) {
                LOGGER.warn("[" + this.logger + "]" + message, args);
            }
        }

    }

    /**
     * 处理异常
     *
     * @param e
     * @author ebert_chan
     */
    private void handlerException(Throwable e) {
        LOGGER.warn((isEnableLogger() ? ("[" + this.logger + "]") : "") + "rpc call throws an exception", e);

        if (thrownTargetException) {
            if (this.code != null) {
                throw new BusinessException(this.code, this.args);
            }
            throw new BusinessException(ResponseCode.BUSY_EXCEPTION);
        }

        if (this.code != null) {
            throw new BusinessException(this.code);
        }
    }

    /**
     * 明确Rpc返回结果不能为空，为空时抛出BussinessException，需要设置返回状态码，可以使用 thrownTargetException() 或 setFailedCode(int code, Object...args)。默认开启空数据检查，关闭日志输出
     *
     * @return
     * @author ebert_chan
     */
    public static RpcBuilder ofNonNull() {
        return RpcBuilder.ofNullable().enableNullDataCheck();
    }

    /**
     * 明确Rpc返回结果可以为空值，在使用时需要自行判空。默认关闭空数据检查，关闭日志输出
     *
     * @return
     * @author ebert_chan
     */
    public static RpcBuilder ofNullable() {
        RpcBuilder builder = new RpcBuilder();
        return builder;
    }

}
